/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.gateway.server.filter;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.iwindplus.boot.domain.constant.CommonConstant.HeaderConstant;
import com.iwindplus.boot.domain.constant.CommonConstant.TraceConstant;
import com.iwindplus.boot.util.HttpsUtil;
import com.iwindplus.boot.util.JacksonUtil;
import com.iwindplus.gateway.server.domain.constant.GatewayConstant;
import com.iwindplus.gateway.server.domain.constant.GatewayConstant.ServerWebExchangeAttributeConstant;
import com.iwindplus.gateway.server.util.GatewayUtil;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest.Builder;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 基础过滤器.
 *
 * @author zengdegui
 * @since 2020/4/15
 */
@Slf4j
@Component
public class BaseFilter implements Ordered, GlobalFilter {

    @Override
    public int getOrder() {
        return GatewayConstant.FilterConstant.FILTER_BASE_ORDER;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().putIfAbsent(ServerWebExchangeAttributeConstant.BEGIN_TIME, System.currentTimeMillis());
        ServerHttpRequest request = exchange.getRequest();
        Map<String, String> headerMap = request.getHeaders().toSingleValueMap();
        String headerStr = JacksonUtil.toJsonStr(headerMap);
        if (CharSequenceUtil.isNotBlank(headerStr)) {
            exchange.getAttributes().putIfAbsent(ServerWebExchangeAttributeConstant.REQUEST_HEADERS, headerStr);
        }
        // 移除防伪造请求头
        GatewayUtil.removeUserHeader(exchange);

        if (log.isInfoEnabled()) {
            // 统计执行时间任务
            StopWatch stopWatch = new StopWatch();
            stopWatch.start(BaseFilter.class.getSimpleName());

            final ServerWebExchange serverWebExchange = this.getServerWebExchange(exchange, request);
            return chain.filter(serverWebExchange)
                .doOnTerminate(() -> {
                    MDC.clear();

                    stopWatch.stop();
                    log.debug(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
                });
        } else {
            final ServerWebExchange serverWebExchange = this.getServerWebExchange(exchange, request);
            return chain.filter(serverWebExchange)
                .doOnTerminate(() -> {
                    MDC.clear();
                });
        }
    }

    @NotNull
    private ServerWebExchange getServerWebExchange(ServerWebExchange exchange, ServerHttpRequest request) {
        Builder builder = request.mutate();
        this.buildHeaderInfo(exchange, builder);
        final ServerWebExchange serverWebExchange = exchange.mutate().request(builder.build()).build();
        return serverWebExchange;
    }

    private void buildHeaderInfo(ServerWebExchange exchange, Builder builder) {
        // 国际化
        final String language = HttpsUtil.getLanguage(exchange.getRequest());
        MDC.put(HttpHeaders.ACCEPT_LANGUAGE, language);

        // 灰度发布
        final Map<String, String> headers = HttpsUtil.getHeaders(exchange.getRequest());
        final String requestedVersion = headers.get(HeaderConstant.X_REQUESTED_VERSION);
        if (ObjectUtil.isNotEmpty(requestedVersion)) {
            MDC.put(HeaderConstant.X_REQUESTED_VERSION, requestedVersion);
        }

        // 真实IP
        final String realIp = GatewayUtil.getRealIp(exchange);
        builder.header(TraceConstant.REAL_IP, realIp);
        MDC.put(TraceConstant.REAL_IP, realIp);
    }
}
